/*
 * Created on Apr 2, 2006
 *
 * Copyright (c) 2006, the JUNG Project and the Regents of the University 
 * of California
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see either
 * "license.txt" or
 * http://jung.sourceforge.net/license.txt for a description.
 */
package edu.uci.ics.jung.graph.util;

import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;

/**
 * An implementation of <code>Collection</code> that stores exactly 2 non-null
 * objects and is not mutable. They respect <code>equals</code> and may be used
 * as indices or map keys.
 * <p>
 * Note that they do not protect from malevolent behavior: if one or another
 * object in the tuple is mutable, then it can be changed with the usual bad
 * effects.
 */
@SuppressWarnings("serial")
public final class Pair<T> implements Collection<T>, Serializable {
	private T first;
	private T second;

	/**
	 * Creates a <code>Pair</code> from the specified elements.
	 * 
	 * @param value1
	 *            the first value in the new <code>Pair</code>
	 * @param value2
	 *            the second value in the new <code>Pair</code>
	 * @throws IllegalArgumentException
	 *             if either argument is null
	 */
	public Pair(T value1, T value2) {
		if (value1 == null || value2 == null) {
			throw new IllegalArgumentException(
					"Pair cannot contain null values");
		}
		first = value1;
		second = value2;
	}

	/**
	 * Creates a Pair from the passed Collection. The size of the Collection
	 * must be 2.
	 * 
	 * @param values
	 *            the elements of the new <code>Pair</code>
	 * @throws IllegalArgumentException
	 *             if the input collection is null, contains null values, or has
	 *             != 2 elements.
	 */
	public Pair(Collection<? extends T> values) {
		if (values == null) {
			throw new IllegalArgumentException(
					"Input collection cannot be null");
		}
		if (values.size() == 2) {
			if (values.contains(null)) {
				throw new IllegalArgumentException(
						"Pair cannot contain null values");
			}
			Iterator<? extends T> iter = values.iterator();
			first = iter.next();
			second = iter.next();
		} else {
			throw new IllegalArgumentException(
					"Pair may only be created from a Collection of exactly 2 elements");
		}

	}

	/**
	 * Creates a <code>Pair</code> from the passed array. The size of the array
	 * must be 2.
	 * 
	 * @throws IllegalArgumentException
	 *             if the input array is null, contains null values, or has != 2
	 *             elements.
	 */
	public Pair(T[] values) {
		if (values == null) {
			throw new IllegalArgumentException("Input array cannot be null");
		}
		if (values.length == 2) {
			if (values[0] == null || values[1] == null) {
				throw new IllegalArgumentException(
						"Pair cannot contain null values");
			}
			first = values[0];
			second = values[1];
		} else {
			throw new IllegalArgumentException(
					"Pair may only be created from an "
							+ "array of 2 elements");
		}
	}

	/**
	 * Returns the first element.
	 */
	public T getFirst() {
		return first;
	}

	/**
	 * Returns the second element.
	 */
	public T getSecond() {
		return second;
	}

	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		}

		if (o instanceof Pair) {
			Pair otherPair = (Pair) o;
			Object otherFirst = otherPair.getFirst();
			Object otherSecond = otherPair.getSecond();
			return (this.first == otherFirst
					|| (this.first != null && this.first.equals(otherFirst)))
					&& (this.second == otherSecond || (this.second != null
							&& this.second.equals(otherSecond)));
		}
		return false;
	}

	@Override
	public int hashCode() {
		int hashCode = 1;
		hashCode = 31 * hashCode + (first == null ? 0 : first.hashCode());
		hashCode = 31 * hashCode + (second == null ? 0 : second.hashCode());
		return hashCode;
	}

	@Override
	public String toString() {
		return "<" + first.toString() + ", " + second.toString() + ">";
	}

	@Override
	public boolean add(T o) {
		throw new UnsupportedOperationException("Pairs cannot be mutated");
	}

	@Override
	public boolean addAll(Collection<? extends T> c) {
		throw new UnsupportedOperationException("Pairs cannot be mutated");
	}

	@Override
	public void clear() {
		throw new UnsupportedOperationException("Pairs cannot be mutated");
	}

	@Override
	public boolean contains(Object o) {
		return (first == o || first.equals(o) || second == o
				|| second.equals(o));
	}

	@Override
	public boolean containsAll(Collection<?> c) {
		if (c.size() > 2) {
			return false;
		}
		Iterator<?> iter = c.iterator();
		Object c_first = iter.next();
		Object c_second = iter.next();
		return this.contains(c_first) && this.contains(c_second);
	}

	@Override
	public boolean isEmpty() {
		return false;
	}

	@Override
	public Iterator<T> iterator() {
		return new PairIterator();
	}

	@Override
	public boolean remove(Object o) {
		throw new UnsupportedOperationException("Pairs cannot be mutated");
	}

	@Override
	public boolean removeAll(Collection<?> c) {
		throw new UnsupportedOperationException("Pairs cannot be mutated");
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		throw new UnsupportedOperationException("Pairs cannot be mutated");
	}

	@Override
	public int size() {
		return 2;
	}

	@Override
	public Object[] toArray() {
		Object[] to_return = new Object[2];
		to_return[0] = first;
		to_return[1] = second;
		return to_return;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <S> S[] toArray(S[] a) {
		S[] to_return = a;
		if (a.length < 2) {
			// TODO check
			// to_return = (S[])java.lang.reflect.Array.newInstance(type, 2);
			Object[] arr = new Object[2];
			to_return = (S[]) arr;
		}
		to_return[0] = (S) first;
		to_return[1] = (S) second;

		if (to_return.length > 2) {
			to_return[2] = null;
		}
		return to_return;
	}

	private class PairIterator implements Iterator<T> {
		int position;

		private PairIterator() {
			position = 0;
		}

		@Override
		public boolean hasNext() {
			return position < 2;
		}

		@Override
		public T next() {
			position++;
			if (position == 1) {
				return first;
			} else if (position == 2) {
				return second;
			} else {
				return null;
			}
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException("Pairs cannot be mutated");
		}
	}
}
